#include <sstream>       // ostringstream
#ifdef DEBUG
#include <iostream>      // std::cout
#include <iomanip>
#endif
#include <fltk/layout.h> // FLTK layout anonymous enumeration
#include <fltk/draw.h>   // FLTK methods for 2D graphics
#include <fltk/damage.h> // damage enumerations

#include <profo/Protein.h>
#include <profo/ProteinView2D.h>

using namespace profo;

/**
 * A typical constructor for a fltk::Group subclass
 */
ProteinView2D::ProteinView2D(Protein& argProtein, ///< A Protein object.
                             int x, ///< X coordinate of the widget
                             int y, ///< Y coordinate of the widget
                             int w, ///< width of the widget
                             int h, ///< height of the widget
                             const char* l ///< label of the widget (null by default)
                            ) :
  fltk::ScrollGroup(x, y, w, h, l, true /* auto-begin() */),
  _proteinRef(argProtein),
  minusY(30, 0, 20, 20, "-Y"), // initial positions of -Y axis label
  minusX(0, 30, 20, 20, "-X"), // initial position of the -X axis label
  plusX((int)(w/30)*30-30, 30, 20, 20, "X"),
  plusY(30, (int)(h/30)*30-30, 20, 20, "Y")
{
  this->resizable(0); // we do not want to resize children of this fltk::Group!
  // Now let's initialise some axis-labels' settings
  initAxisLabels();
  type(fltk::ScrollGroup::BOTH_ALWAYS); // always show scrollbars.
  _step = 30;
  _asize = 20;
  _hcolor = (fltk::Color)0x90909000;
  _pcolor = fltk::WHITE;
  _oldColor = fltk::BLACK;
  _scolor = (fltk::Color)0x55FF5500;
  _selectedPtr = (fltk::Button*)0;
  end(); // don't forget to do this!
}

/**
 * This method is used to translate from protein coordinates to View's coordinates.
 * Please note that it actually gives the X coordinate of the top-left corner of
 * amino acid button.
 * @param arg int An X-coordinate of an amino acid in the Protein's coordinate system.
 */
int ProteinView2D::transX(int arg) {
  int rx;
  int myx = minusY.x(); // get information about where in View's coordinate system is
                    // the Y axis.
  rx = arg*_step+myx;
  return rx;
} // transX() method

/**
 * This method is used to translate from protein coordinates to View's coordinates.
 * Please note that it actually gives the Y coordinate of the top-left corner of
 * amino acid button.
 * @param arg int An Y-coordinate of an amino acid in the Protein's coordinate system.
 */
int ProteinView2D::transY(int arg) {
  int ry;
  int mxy = minusX.y(); // get information about where in View's coordinate system is
                    // the X axis.
  ry = arg*_step + mxy;
  return ry;
} // transY() method



/**
 * Removes all amino acids (fltk::Button objects) in the view.
 *
 * Complexity: O(n) where n = number of amino acids in the protein.
 */
void ProteinView2D::clearView() {
  if (_buttons.empty()) return;
  for (int i=0; _buttons.size(); i++) {
    fltk::Button* tmp = _buttons.back(); // get the last element int he vector (it is a pointer)
    _buttons.pop_back(); // remove last element
    delete tmp; // delete the button
  } // for
  _buttons.clear(); // just to be sure.
} // clearView() method

/**
 * A callback which handles user-click on an amino acid button.
 */
void ProteinView2D::acidClickCb(fltk::Button* argButton, void* data) {
  fltk::Button* buttonPtr = reinterpret_cast<fltk::Button*>(argButton);
  ProteinView2D* thisPtr = reinterpret_cast<ProteinView2D*>(buttonPtr->parent());
  AminoAcid* acidPtr = reinterpret_cast<AminoAcid*>(data);
  fltk::Button* oldPtr = thisPtr->selected();
  // now let's check if we clicked on an amino acid that hasn't been selected
  if (oldPtr != buttonPtr) {
    if (oldPtr != 0) { // has an acid been selected previously?
      oldPtr->color(thisPtr->oldColor()); // if so, return its previous color
      oldPtr->redraw(); // redraw the button
    }
    thisPtr->selected(buttonPtr);
    thisPtr->oldColor(buttonPtr->color()); // save currently selected color
    buttonPtr->color(thisPtr->sColor());   // highlight the amino acid
    buttonPtr->redraw(); // redraw the button
    thisPtr->protein().selected(acidPtr->id); // inform the model about selected acid
  } // if
  unsigned char freeMoves = acidPtr->free;
  thisPtr->mainViewPtr->updateControlButtons(freeMoves);
#ifdef DEBUG
  std::cout << acidPtr->toString() << std::endl;
  std::cout << acidPtr->id << std::endl;
  std::cout << std::hex << (int)(acidPtr->free) << std::endl;
#endif
}

/**
 * update() method is called whenever Model's state is changed and view needs to
 * be refreshed.
 */
void ProteinView2D::update() {
  if (_proteinRef.size() == 0) {
    // Protein contains no amino acids, we can safely return.
    // if input sequence is empty we will delete all amino acids.
    this->clearView();
    return;
  } else {
    fltk::Button* tmpButton = 0;
    _tooltips.resize(_proteinRef.size());
    // we do not have exact number of fltk::Buttons to show the Protein object
    // so we first delete all buttons that are currently in use (except axis labels).
    // WARNING: we must not use fltk::Group's clear() method, because it will delete
    // all children!
    this->clearView();
    // generate buttons again, and place them at proper places.
    this->begin();
    for (int i=0; i<_proteinRef.size(); i++) {
      AminoAcid& obj = _proteinRef.get(i);
      std::ostringstream tmp;
      tmp << "idx: " << i << "\ntype: ";
      tmpButton = new fltk::Button(transX(obj.x), transY(obj.y), _asize, _asize, (obj.h ? "H":"P"));
      _buttons.push_back(tmpButton);
      if (obj.h) {
        tmpButton->color(_hcolor);
        tmp << "hydrophobic\nX: " << obj.x << " Y: " << obj.y;
        _tooltips[i] = tmp.str();
        tmpButton->tooltip(_tooltips[i].c_str());
      } else {
        tmpButton->color(_pcolor);
        tmp << "polar\nX: " << obj.x << " Y: " << obj.y;
        _tooltips[i] = tmp.str();
        tmpButton->tooltip(_tooltips[i].c_str());
      } // else
      // Change the color of the selected aminoacid.
      if (_proteinRef.selected() == i) {
        _selectedPtr = tmpButton;
        tmpButton->color(_scolor);
      }
      // Set the callback and pass a pointer to the amino acid object as
      // user-defined data.
      tmpButton->callback((fltk::Callback*)acidClickCb, &obj);
      // Move widget with the "+X" label at proper position (one step to the right
      // of the last amino cid button.
      if (plusX.x() < tmpButton->x())
        plusX.x(tmpButton->x()+_step);
    } // for
    this->end();
  } // else
} // update() method

/**
 * A method which draws connections between amino acids, on the background of
 * this ProteinView2D widget.
 */
void ProteinView2D::drawConnections() {
  // return immediately if we have only one amino acid in the protein chain.
  if (_proteinRef.size() <= 1)
    return;
  for (int i=0; i<_proteinRef.size()-1; i++) {
    AminoAcid first = _proteinRef.get(i);
    AminoAcid next = _proteinRef.get(i+1);
    // Now we must translate from proteins coordinate into View's coordinate.
    int fx = transX(first.x)+10;
    int fy = transY(first.y)+10;
    int nx = transX(next.x)+10;
    int ny = transY(next.y)+10;
    fltk::setcolor(fltk::BLUE);
    fltk::line_style(fltk::SOLID, 4);
    fltk::drawline(fx, fy, nx, ny);
    // now reset color back to black
    fltk::setcolor(fltk::BLACK);
  } // for
} // drawConnections() method

/**
 * This method is responsible for drawing ProteinView2D's background, including
 * axes, but NOT axis labels - labels are independent widgets. We use positions
 * of these widgets to calculate positions in the ProteinView2D's, independent
 * coordinate system (every subclass of fltk::Group has its own coordinate system).
 */
void ProteinView2D::drawBackground() {
  int mxx = minusX.x();
  int mxy = minusX.y();
  int myx = minusY.x();
  int myy = minusY.y();
  int pxx = plusX.x();
  int pxy = plusX.y();
  int pyx = plusY.x();
  int pyy = plusY.y();
  fltk::line_style(fltk::DOT); // set the current line style to dotted
  // vertical grid
  for (int i = mxx; i<pxx+_step; i=i+_step) {
    fltk::drawline(i+10, myy+10, i+10, pyy+10);
  }
  // horisontal grid
  for (int i = myy; i<pyy+_step; i=i+_step) {
    fltk::drawline(mxx+10, i+10, pxx+10, i+10);
  }
  // draw connections
  drawConnections();
  // draw axes
  fltk::line_style(fltk::SOLID, 1);
  fltk::drawline(mxx-_step, mxy+10, pxx+_step, pxy+10); // X axis
  fltk::drawline(myx+10, myy-_step, pyx+10, pyy+_step); // Y axis
  // we must reset the style of lines so the rest of the GUI renders properly
  fltk::line_style(fltk::SOLID);
} // drawBackground() method

/**
 * Responsibility of this method is to initialise settings for all 4 axis labels.
 */
void ProteinView2D::initAxisLabels() {
  minusX.box(fltk::FLAT_BOX);
  minusX.buttonbox(fltk::NO_BOX);
  minusX.color(fltk::GRAY50);
  minusX.textcolor(fltk::WHITE);
  minusX.labelcolor(fltk::WHITE);
  minusY.box(fltk::FLAT_BOX);
  minusY.buttonbox(fltk::NO_BOX);
  minusY.color(fltk::GRAY50);
  minusY.textcolor(fltk::WHITE);
  minusY.labelcolor(fltk::WHITE);
  plusX.box(fltk::FLAT_BOX);
  plusX.buttonbox(fltk::NO_BOX);
  plusX.color(fltk::GRAY50);
  plusX.textcolor(fltk::WHITE);
  plusX.labelcolor(fltk::WHITE);
  plusY.box(fltk::FLAT_BOX);
  plusY.buttonbox(fltk::NO_BOX);
  plusY.color(fltk::GRAY50);
  plusY.textcolor(fltk::WHITE);
  plusY.labelcolor(fltk::WHITE);
} // initAxisLabels() method

/**
 * Overloaded fltk::ScrollGroup::draw() method. This this method is called
 * whenever ProteinView2D object needs to redraw itself.
 */
void ProteinView2D::draw() {
  fltk::ScrollGroup::draw();
  Rectangle r;
  bbox(r);
  fltk::push_clip(r);
  drawBackground();
    for (int i = children(); i--;) {
	    Widget& w = *child(i);
      draw_child(w);
    } // for
  fltk::pop_clip();
/*
  fltk::Widget* const* a;
  if (damage() == fltk::DAMAGE_CHILD) { // only redraw some children
    for (int i = children(); i--;) {
	    Widget& w = *child(i);
      update_child(w);
    } // for
  } else { // total redraw
    drawBackground();
    // now draw all the children atop the background
    for (int i = children(); i--;) {
	    Widget& w = *child(i);
      draw_child(w);
    } // for
  } // else
*/
} // draw() method

// $Id: ProteinView2D.cpp 42 2009-04-26 04:31:13Z dejan.lekic $
